<?php
class TasksService extends Service{

    /**
     * @param integer $projectId
     * <pre>
     * {
     *   "type":"integer"
     * }
     * </pre>
     * @return object
     * <pre>
     * {
     *   "type":"object",
     *   "properties":{},
     *   "additionalProperties":true
     * }
     * </pre>
     */
    public static function loadJsonTree($projectId)
    {
        if($projectId) {
          $lftAndRgt = ProjectTable::getLftAndRgtByProjectId($projectId);
        }
        if (self::_invoke()) return self::_output();
        $q =
            Doctrine_Query::create()
                ->select('t.*, ')
                ->from('DevTask t')
                ->where('t.lft >= ? AND rgt <= ?',$lftAndRgt)
                ->setHydrationMode(Doctrine::HYDRATE_RECORD);
        $tree = Doctrine::getTable('DevTask')->getTree();
        $tree->setBaseQuery($q);
        $tree = $tree->fetchTree();
        //Dbg::x($tree->toArray());
        return self::buildJsonTree($tree);
    }


    /**
     * Lê um item em um formato para ser convertido em JSON
     *
     * @param integer $json
     * <pre>
     * {
     *   "type":"integer"
     * }
     * </pre>
     * @return object
     * <pre>
     * {
     *   "type":"object",
     *   "properties":{},
     *   "additionalProperties":true
     * }
     * </pre>
     */
    public static function loadJsonItem($id)
    {
        if (self::_invoke()) return self::_output();
        $item = Doctrine::getTable('DevTask')->find($id);
        return self::buildJsonItem($item);
    }

    private static function buildJsonTree($items) {
        $arStack = array();
        foreach($items as $item) {
            $level = $item['level'];
            if(count($arStack) == 0) {
                $firstLevel = $level;
                $arStack[$level][] = self::buildJsonItem($item);
            }
            else {
                if($lastItem['level'] < $level) {
                    $arStack[$level][] = self::buildJsonItem($item);
                }
                elseif($lastItem['level'] > $level) {
                    self::recoverOpenItens($arStack,$level);
                    unset($arStack[$level+1]);
                    $arStack[$level][] = self::buildJsonItem($item);
                } else {
                    $arStack[$level][] = self::buildJsonItem($item);
                }
            }
            $lastItem = $item;
        }
        self::recoverOpenItens($arStack, $firstLevel);
        return $arStack[$firstLevel];
    }

    private static function recoverOpenItens(&$arStack,$level) {
        foreach(array_reverse(array_keys($arStack)) as $key) {
            if($key > $level) {
                if(
                    !isset($arStack[$key-1][count($arStack[$key-1])-1]) ||
                    !isset($arStack[$key-1][count($arStack[$key-1])-1]['children'])
                ) {
                    $arStack[$key-1][count($arStack[$key-1])-1]['children'] = array();
                }
                $arStack[$key-1][count($arStack[$key-1])-1]['children'] = $arStack[$key];
                unset($arStack[$key]);
            }
        }
    }
    
    private static function buildJsonItem($obj) {
        //Dbg::x( $obj->toArray() );
        return array(
            'attributes' => array(
                'id' => 'category-item-'.$obj['id'],
                'metadata' => json_encode(array(
                    'name'=>$obj['name'],
                    'id'=>$obj['id']
                ))
            ),
            'data' => array(
                'title'=>$obj['name'].($obj['tests_result'] !== TestResult::SUCCESS && $obj['tests_result'] ? '*' : ''),//.$obj['lft'].'-'.$obj['rgt'],
                'icon'=>'/images/taskIcons/folder-'.$obj->getState().'.jpg'
            )
        );
    }

    /**
     * Remove uma categoria com todos seus filhos.
     * Da erro se tiver trabalhos relacionados.
     *
     * @param object $data
     * <pre>
     * {
     *   "type":"object",
     *   "properties": {
     *     "taskId":{"type":"integer"},
     *     "projectId":{"type":"integer"}
     *   }
     * }
     * </pre>
     * @return void
     */
    public static function delete($data) {
        if (self::_invoke()) return self::_output();
        $task = Doctrine::getTable('DevTask')->find($data->taskId);
        if($task->getNode()->isRoot()) {
            throw new ServiceException('Can´t delete the root.');
        }
        $children = $task->getNode()->getChildren();
        if($children && $children->count()) {
            throw new ServiceException('Can´t delete a node with children.');
        }
        foreach($task->TaskStateLog as $log) {
            $log->delete();
        }
        $task->getNode()->delete();
        TasksService::calculateResultsAndStates($data->projectId);
    }


    /**
     * Renomeia
     * @param object $json
     * <pre>
     * {
     *   "type":"object",
     *   "properties":{
     *     "id":{"type":"integer"},
     *     "name":{"type":"string","minLength":1}
     *   }
     * }
     * </pre>
     * @return void
     */
    public static function rename($json) {
        if (self::_invoke()) return self::_output();
        $category = Doctrine::getTable('DevTask')->find($json->id);
        $category->name = $json->name;
        $category->save();
    }
    



    /**
     * Insere uma item
     *
     * @param object $json
     *
     * <pre>
     * {
     *   "type":"object",
     *   "properties":{
     *     "referenceId":{"type":"integer"},
     *     "relativePosition":{"type":"string","enum":["inside","after","before"]},
     *     "name":{"type":"string", "minLength":1}
     *   }
     * }
     * </pre>
     * @return int inserted category id
     * <pre>
     * {"type":"integer"}
     * </pre>
     */
    public static function insert($json) {
        if (self::_invoke()) return self::_output();
        $referenceCat = DevTaskTable::findWithProjectId($json->referenceId);
        $cat = new DevTask;
        $cat->name = $json->name;
        if($json->relativePosition == 'inside') {
            $parentCat = $referenceCat;
        }
        else {
            $parentCat = $referenceCat->getNode()->getParent();
        }
        if(!$parentCat->getNode()->hasChildren() && $parentCat->state !== DevTask::BACKLOG) {
            throw new NodeInsertionFailedException('You can´t insert a node inside a non backlog leaf node.');
        }
        $cat->project_id = $parentCat->project_id;
        switch($json->relativePosition) {
          case 'inside':
              $cat->getNode()->insertAsLastChildOf($referenceCat);
              break;
          case 'after':
              $cat->getNode()->insertAsNextSiblingOf($referenceCat);
              break;
          case 'before':
              $cat->getNode()->insertAsPrevSiblingOf($referenceCat);
              break;
        }
        TasksService::calculateResultsAndStates($referenceCat->project_id);
        return $cat->id;
    }
    

    /**
     * Move um item de lugar
     * @param object $json
     * <pre>
     * {
     *   "type":"object",
     *   "properties":{
     *     "referenceId":{"type":"integer"},
     *     "moveId":{"type":"integer"},
     *     "relativePosition":{"type":"string","enum":["inside","after","before"]}
     *   }
     * }
     * </pre>
     * @return void
     */
    public static function move($json) {
        if (self::_invoke()) return self::_output();
        $moved     = Doctrine::getTable('DevTask')->find($json->moveId);
        $oldParent = $moved->getNode()->getParent();
        $reference = Doctrine::getTable('DevTask')->find($json->referenceId);
        // define o próximo pai
        if( in_array($json->relativePosition,array('after','before')) ) {
            $parent = $reference->getNode()->getParent();
            if(!$parent) {
                throw new ServiceException('Cant create new root.');
            }
        }
        else {
            $parent = $reference;
        }
        // da erro se o novo pai for filho ou descendente no nó sendo movido.
        if($parent->getNode()->isDescendantOfOrEqualTo($moved)) {
            throw new ServiceException('Tried to paste a task into itself or one of its descendants.');
        }
        switch($json->relativePosition) {
            case 'after':
                $moved->getNode()->moveAsNextSiblingOf($reference);
                break;
            case 'before':
                $moved->getNode()->moveAsPrevSiblingOf($reference);
                break;
            case 'inside':
                $moved->getNode()->moveAsFirstChildOf($reference);
                break;
        }
        /*
        TaskLogTable::log(
            $oldParent->id,
            'Subtask "'.$moved->name.'" moved to '.$json->relativePosition.' task "'.$reference->name.'"'
        );
        TaskLogTable::log(
            $moved->id,
            'Moved '.$json->relativePosition.' task "'.$reference->name.'"'
        );
        */
    }

    /**
     *
     * @param object $json
     * <pre>
     * {
     *   "type":"object",
     *   "properties":{
     *     "id":{"type":"integer"},
     *     "state":{"type":"string","enum":["BACKLOG","SPRINT","WORK_IN_PROGRESS","DONE"],"optional":true},
     *     "estimated":{"type":"string","optional":true},
     *     "description":{"type":"string","optional":true},
     *     "how_to_show":{"type":"string","optional":true},
     *     "technical_aspects":{"type":"string","optional":true}
     *   }
     * }
     * </pre>
     * @return void
     */
    public static function edit($json) {
        if (self::_invoke()) return self::_output();
        $task = DevTaskTable::findWithProjectId($json->id);
        $calculate = false;
        if( array_key_exists('state',$json) ) {
            $task->state = $json->state;
            $calculate = true;
        }
        if( array_key_exists('description',$json) ) {
            $task->description = $json->description;
        }
        if( array_key_exists('how_to_show',$json) ) {
            $task->how_to_show = $json->how_to_show;
        }
        if( array_key_exists('technical_aspects',$json) ) {
            $task->technical_aspects = $json->technical_aspects;
        }
        if( array_key_exists('estimated',$json) ) {
            $task->estimated = $json->estimated;
        }
        if(
            $task->getNode()->hasChildren() &&
            $task->isStateModified()
        ) {
            throw new ServiceException('Only leaf nodes can have their states altered by the user.');
        }
        $task->save();
        if($calculate) {
            TasksService::calculateResultsAndStates($task->project_id);
        }
    }


    /**
     * @param int id
     * @return object
     * {
     *   "type":"object",
     *   "properties":{
     *      "id":{"type":"string"},
     *      "name":{"type":"string"},
     *      "description":{"type":"string"},
     *      "how_to_show":{"type":"string"},
     *      "technical_aspects":{"type":"string"},
     *      "sumSuccess":{"type":"string"},
     *      "sumIncomplete":{"type":"string"},
     *      "sumError":{"type":"string"},
     *      "sumFailure":{"type":"string"},
     *      "state":{"type":"string","enum":["BACKLOG","SPRINT","WORK_IN_PROGRESS","DONE"]},
     *      "result":{"type":"string"},
     *      "estimated":{"type":"string"},
     *      "lft":{"type":"string"},
     *      "rgt":{"type":"string"},
     *      "level":{"type":"string"},
     *      "count_test":{"type":"integer"},
     *      "has_children":{"type":"boolean"},
     *      "path":{
     *        "type":"array",
     *        "items":{
     *          "type":"array",
     *          "items":{"type":"string"}
     *        }
     *     }
     *   }
     * }
     */
    public static function get($id) {
        $item = Doctrine::getTable('DevTask')->find($id);
        $ar = $item->toArray();
        $ar['count_test'] = $item->Test->count();
        $ar['path'] = $item->getPath();
        $ar['has_children'] = $item->getNode()->hasChildren();
        return $ar;
    }


    /**
     * @param int id
     * @return object
     * {
     *   "type":"object",
     *   "properties":{
     *      "classRelatedCount":{"type":"object"},
     *      "classOrphanCount":{"type":"object"},
     *      "tests":{"type":"object"}
     *   }
     * }
     */
    public static function getRelatedTests($taskId) {
        $node = DevTaskTable::findWithProjectId($taskId);
        $arRelatedTests = array();
        foreach($node->Test as $test) {
            $arRelatedTests[] = $test->id;
        }
        $classRelatedCount = array();
        foreach(TestTable::findAllTestsOrderedByClass($node['project_id']) as $test) {
            $testId = $test->get('id');
            $related = in_array($testId,$arRelatedTests);
            if(!array_key_exists($test->get('class'),$classRelatedCount)) {
                $classRelatedCount[$test->get('class')] = 0;
            }
            $tests[$test['dir']][$test['class']][$testId] =
            array(
                'testName' => $test->getReadableTestName(),
                'related' => $related,
                'numTasks' => $test->numTasks
            );
            if($related) {
                $classRelatedCount[$test->get('class')]++;
            }
        }
        return array(
          'classRelatedCount'=>$classRelatedCount,
          'tests'=>$tests
        );
    }

    /**
     * @param int $projectId
     * @return void
     */
    public static function calculateResultsAndStates($projectId) {
        $projectRoot = Doctrine::getTable('DevTask')->findByProjectId($projectId);
        $projectRoot->calculateTasksStatesAndResultsRecursive($projectId);
    }


}